package server;

import tools.*;

import java.io.IOException;
import java.net.Socket;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;

/**
 * Obsługa połączenia jednego klienta - osobny wątek
 * @author Mateusz Domagała
 */
public class ClientService implements Runnable
{
	/** wskaźnik na obiekt serwera */
   ServerBadugi server;
	/** gniazdo z połączeniem klienta */
   private Socket socket;
	/** nazwa klienta na podstawie gniazda */
   private String nazwa;
   /* id gracza = id watku */
   int id;
   /* wskaznik na gracza */
   Player player;
      
   ObjectOutputStream out = null; // streamy do obslugi komunikacji
   ObjectInputStream in = null;
   
   /* zmienna uzywana do blokowania metody wysylajacej komuniakt do klienta ktora ma czekac na odpowiedz */
   private boolean isAnswer = false; // sprawdzenie czy klient odpowiedzial
   private Protocol protocol; // zapisanie odpowiedzi
   
	/**
	 * Konstruktor
	 * @param socket gniazdo z aktywnym połączeniem z klientem
	 * @param server wskaźnik na obiekt serwera
	 */
   ClientService(Socket socket, ServerBadugi server) throws IOException
   {
	     
		/** zapisanie wskaźników w zmiennych klasowych */
      this.server = server;
      this.socket = socket;
		/** pobranie nazwy klienta - adresu */
      this.nazwa = socket.getRemoteSocketAddress().toString();
      System.out.println("Polaczenie nowego klienta z adresu: " + nazwa);

      try
      {
      	/** pobranie strumieni do pisania i czytania w trakcie komunikacji z klientem */
    	  	out = new ObjectOutputStream(socket.getOutputStream());
    	  	in = new ObjectInputStream(socket.getInputStream());
    	  
      }
      catch(IOException e)
      {
         System.out.println("Blad przy inicjalizacji komunikacji z klientem: " + nazwa);
         throw e;
      }
   }

   void sendResponse(Protocol p)
   {
	   Message m = new Message(p, id, server.game.gameState);
	   try
	   {
	   	out.reset(); // reset kanalu, potrzebny przy aktualizacji gamestate !!!
		   out.writeObject(m);
		   
		   System.out.println(m.p + " - wyslano do klienta: [id=" + this.id +"]" + nazwa);
		   
	   }
	   catch(IOException e)
	   {
		   System.out.println("Blad przy wysylaniu komunikatu [" + p + "] do klienta: [id=" + this.id +"]" + nazwa);
	   }
   }
   
   synchronized Protocol sendResponseAndWait(Protocol p)
   {
   	this.isAnswer = false;
	   Message m = new Message(p, this.id, this.server.game.gameState);
	   try
	   {
	   	out.reset();
		   out.writeObject(m);
		   
		   System.out.println(m.p + " - wyslano do klienta: [id=" + this.id +"]" + nazwa);
	   }
	   catch(IOException e)
	   {
		   System.out.println("Blad przy wysylaniu komunikatu [" + p + "] do klienta: [id=" + this.id +"]" + nazwa);
	   }
	   /* czekaj na odpowiedz */
	   while (!isAnswer) 
	   {
	   	if (!server.game.arePlayersToGame())
	   	{
	   		/* jesli w trakcie czekania na odpowiedz klienta inni gracze powychodzii to nie czekaj dluzej */
	   		return Protocol.QUIT;
	   	}
	   	try
			{
				Thread.sleep(500);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}		
	   }
	   return this.protocol;
   }

   Protocol readMessage()
   {
   	Message message;
   	try
   	{
   		/** odczyt zadania od klienta */
     	 	message = (Message) in.readObject();
     	 	
     	 	if (message.gameState != null )
     	 	{
     	 		/* 
     	 		 * nadpisz dane tylko o graczu ktory wysyla wiadomosc
     	 		 * tutaj mozna zrobic zabezpieczenie przed oszustwem przez klienta
     	 		 * 
     	 		 * */
     	 		player = message.gameState.players.get(id); // ustawienie zmiennej player w klasie ClientService pobranego z message od clienta
     	 		server.game.gameState.players.set(id, player); // wstawienie gracza do servera, biore message klienta i zapisuje o danym kliencie w serverze
     	 	}
     	 	
     	 	System.out.println(message.p + " - otrzymano od klienta: [id=" + this.id +"]" + nazwa);
     	 	return message.p;
      }
      catch (IOException | ClassNotFoundException e) 
      {
      	System.out.println(e.getMessage() +". Blad komunikacji z klientem: [id=" + this.id +"]" + nazwa);
         return Protocol.QUIT;
      }
   }
   
   /**
    * Przesłonięcie metody run() z klasy/interfejsu Runnable
	 * Wykonywana przez cały czas trwania połączenia.
    */
   public void run()
   {
   	/**
   	 * czekanie na poczatek konwersacji - sprawdzenie czy klient jest prawidlowy
   	 **/
   	protocol = readMessage();
   	
   	if(protocol != Protocol.HELLO) // "shake hand" - sprawdzenie poprawnosci klienta
   	{
   		closeConnection();
   		return;
   	}
   	/** 
   	 * Jesli nie ma miejsca to pozegnanie, w przeciwnym przypadku zwiekszenie liczby graczy, utworzenie nowego gracza
   	 **/ 	
   	if (server.game.gameState.countPlayers() < server.numberOfPlayers)
	  	{
		  	/*
		    * dodanie nowego polaczenia do puli. blokada servera na wypadek wielu polaczen jednoczesnie
		    **/
		  	server.game.addPlayer(this); // dodanie playera, nadanie ID w game metoda addPlayer
		  	sendResponse(Protocol.WELCOME); // odeslanie powitania wraz z ID
	  	}
	  	else
	  	{
		  	sendResponse(Protocol.NO_PLACE);
		  	closeConnection();
		  	return;
	  	}
   	
	  	/** Główna pętla wątku obsługującego klienta */
      while(true)
      {
      	protocol = readMessage();
      	/* odblokuj metode Send czekajaca na odpowiedz */
      	isAnswer = true;
      	
         if (protocol == Protocol.QUIT)
			{
				break;
			}
      }
      /** po wyjściu z pętli zamknięcie strumieni i gniazda klienta */
      closeConnection();
   }
   
   void closeConnection()
   {
   	try 
	   {
   		in.close();
			out.close();
			socket.close();
			server.game.removePlayer(this);
			System.out.println("Koniec polaczenia z klientem: [id=" + this.id +"]" + nazwa);
		}
		catch (IOException e) 
		{
			System.out.println("Blad przy zamknieciu polaczenia z klientem: [id=" + this.id +"]" + nazwa);
			return;
		}
   }
   
	public boolean equals(Object o)
	{
		return ((ClientService)o).id == this.id;
	}
}

